// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 内存分配器。
// 
// 这最初是基于tcmalloc的，但有很大的分歧。
// http:

// 主分配器在页面运行时工作。
// 小的分配大小（不超过32 kB）是
// 四舍五入到大约70个大小的类中的一个，每个类都有自己的大小正好相同的自由对象集。
// 任何空闲内存页都可以拆分为一组大小相同的对象
// 然后使用空闲位图进行管理。
// 
// 分配器的数据结构是：
// 
// fixalloc：用于固定大小堆外对象的自由列表分配器，
// 用于管理分配器使用的存储。
// mheap:malloc堆，以页（8192字节）的粒度进行管理。
// mspan：由mheap管理的一系列正在使用的页面。
// mcentral：收集给定大小类的所有跨度。
// mcache：具有可用空间的MSPAN的per-P缓存。
// mstats:分配统计信息。
// 
// 分配一个小对象将按照缓存的层次结构进行：
// 
// 1。将尺寸四舍五入到小尺寸类
// 中的一个，并查看此P的mcache中相应的mspan。
// 扫描mspan的空闲位图以查找空闲插槽。
// 如果有空闲插槽，请分配它。
// 这一切都可以在不获取锁的情况下完成。
// 
// 2。如果mspan没有可用插槽，请从McCentral的具有所需大小
// 。
// 类且具有可用空间的mspan列表中获取新的mspan 
// 获得整个跨度将分摊锁定mcentral的成本。
// 
// 3。如果mcentral的mspan列表为空，请从mheap获取用于mspan的运行
// 页面。
// 
// 4。如果mheap是空的或没有足够大的页面运行，
// 从
// 操作系统分配一组新的页面（至少1MB）。分配大量页面
// 分摊与操作系统对话的成本。
// 
// 扫掠mspan并释放其上的对象将进入类似的
// 层次结构：
// 
// 1。如果mspan被扫掠以响应分配，它
// 将返回给mcache以满足分配。
// 
// 2。否则，如果mspan中仍然有已分配的对象，
// 它将被放置在mspan大小
// 类的mcentral free列表中。
// 
// 3。否则，如果mspan中的所有对象都是空闲的，则mspan的
// 页面将返回给mheap，mspan现在已失效。
// 
// 分配和释放大型对象直接使用mheap 
// 绕过mcache和mcentral。
// 
// 。如果needzero为false，那么mspan中的空闲对象插槽
// 已归零。否则，如果needzero为true，则对象被归零为
// 它们已分配。延迟归零有多种好处：
// 
// 1。堆栈帧分配可以完全避免归零。
// 
// 2。它表现出更好的时间局部性，因为程序是
// 可能即将写入内存。
// 
// 3。我们没有零个永远不会被重用的页面。

// 虚拟内存布局
// 
// 堆由一组竞技场组成，64位为64MB，32位为
// 4MB（heapArenaBytes）。每个竞技场的起始地址也是
// 与竞技场大小一致。
// 
// 每个竞技场都有一个关联的heapArena对象，该对象存储该竞技场的
// 元数据：竞技场中所有单词的堆位图
// 以及竞技场中所有页面的跨度图。heapArena对象是
// 它们自己被分配到堆外。
// 
// 由于竞技场是对齐的，所以地址空间可以被视为一系列竞技场框架。
// 竞技场帧号为*heapArena，或零表示部分地址
// 未由Go堆支持的空间。竞技场地图是由一个“L1”竞技场地图和多个“L2”竞技场
// 地图组成的两级数组；然而，由于竞技场很大，在许多体系结构上，
// 竞技场地图由一个较大的L2地图组成。
// 
// 竞技场地图覆盖了整个可能的地址空间，允许Go堆使用地址空间的任何部分。分配器
// 试图保持竞技场连续，以便大跨度（因此
// 大对象）可以跨竞技场。

package runtime

import (
	"internal/goarch"
	"internal/goos"
	"runtime/internal/atomic"
	"runtime/internal/math"
	"runtime/internal/sys"
	"unsafe"
)

const (
	debugMalloc = false

	maxTinySize   = _TinySize
	tinySizeClass = _TinySizeClass
	maxSmallSize  = _MaxSmallSize

	pageShift = _PageShift
	pageSize  = _PageSize
	pageMask  = _PageMask
	// 按照构造，最小对象类的单页跨距
	// 每个跨距的对象数最多。
	maxObjsPerSpan = pageSize / 8

	concurrentSweep = _ConcurrentSweep

	_PageSize = 1 << _PageShift
	_PageMask = _PageSize - 1

	// /_64bit=1（在64位系统上），0（在32位系统上）
	_64bit = 1 << (^uintptr(0) >> 63) / 2

	// 微小分配器参数，请参阅malloc中的“微小分配器”注释。去
	_TinySize      = 16
	_TinySizeClass = int8(2)

	_FixAllocChunk = 16 << 10 // FixAlloc的块大小

	// /Per-P、Per-order堆栈段缓存大小。
	_StackCacheSize = 32 * 1024

	// 获取缓存的订单数。订单0是FixedStack 
	// 并且每个连续订单都是两倍大。
	// 我们希望缓存2KB、4KB、8KB和16KB堆栈。更大的堆栈
	// 将直接分配。
	// 由于FixedStack在不同的系统上是不同的，我们必须改变NumStackOrders以保持相同的最大缓存大小。
	// OS | FixedStack | NumStackOrders 
	// /------------------+-------------------
	// linux/darwin/bsd | 2KB | 4 
	// windows/32 | 4KB | 3 
	// windows/64 | 8KB | 2 
	_NumStackOrders = 4 - goarch.PtrSize/4*goos.IsWindows - 1*goos.IsPlan9

	// amd64上，地址被符号扩展到HEAPARBITS之外。在
	// 其他拱门上，它们是零延伸的。
	// 
	// 在大多数64位平台上，基于硬件和操作系统限制的组合，我们将其限制为48位。
	// 
	// amd64硬件将地址限制为48位，将符号扩展
	// 限制为64位。前16位不是
	// 所有0或所有1的地址都是“非规范”且无效的。由于
	// 这些“负”地址，在将索引计算到
	// 堆arenas索引之前，我们在amd64上将地址偏移1。2017年，amd64硬件增加了
	// 对57位地址的支持；然而，目前只有Linux 
	// 支持此扩展，内核永远不会选择1<<47以上的
	// 地址，除非使用提示
	// 1<<47以上的地址调用mmap（我们从来不会这样做）。
	// 
	// arm64硬件（从ARMv8开始）将用户地址限制为48个
	// 位，在范围内[0,1<<48）.在硬件上，
	// 
	// ppc64、mips64和s390x支持任意64位地址
	// 在Linux上，Go依赖于更严格的操作系统限制。在Linux的处理器上，基于
	// 的用户地址空间受限为
	// 遵循64位体系结构：
	// 
	// 体系结构名称最大值（不含）
	// /---------------------------------------------------------------
	// amd64任务大小最大0x007FFFFFF000（47位地址）
	// arm64任务大小0x01000000000000（48位地址）
	// ppc64任务大小用户64 0x004000000000000（46位地址）
	// mips64{，le}任务大小64 0x0001000000000（40位地址）
	// s390x任务大小1<<64（64位地址）
	// 
	// 这些限制可能会随着时间的推移而增加，但目前处于
	// 除s390x外的大多数48位。在所有体系结构上，Linux 
	// 开始在地址
	// 显著低于48位的位置放置mmap区域，因此即使
	// 可能超过Go的48位限制，在
	// 实践中也极不可能。在32位平台上，我们接受完整的32位地址，因为这样做很便宜。
	// mips32只能访问低2GB的虚拟内存，因此
	// 我们进一步将其限制为31位。
	// 
	// 在ios/arm64上，虽然64位指针可能是
	// 可用，但在ios<14时指针被截断为33位。
	// 此外，只有地址空间的前4个GiB是
	// 实际可用于应用程序。在iOS>=14中，更多的
	// 地址空间可用，操作系统现在可以提供33位之外的地址。选择40位
	// 作为
	// 页面分配器的地址空间使用和mmap区域的灵活性之间的合理平衡
	// 我们将接受堆。我们不能只移动到完整的
	// 48位，因为对于旧的
	// iOS版本，这会占用太多的地址空间。
	// TODO（mknyszek）：一旦iOS<14被弃用，将iOS/arm64 
	// 升级到48位地址空间，就像其他所有arm64平台一样。
	// 
	// WebAssembly当前的线性内存限制为4GB。
	heapAddrBits = (_64bit*(1-goarch.IsWasm)*(1-goos.IsIos*goarch.IsArm64))*48 + (1-_64bit+goarch.IsWasm)*(32-(goarch.IsMips+goarch.IsMipsle)) + 40*goos.IsIos*goarch.IsArm64

	// maxAlloc是分配的最大大小。在64位上，
	// 理论上可以分配1个<<heapdRbits字节。然而，在32位上，这个值小于1，因为地址空间中的字节数实际上不适合uintptr中的字节数。
	maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1

	// 堆地址中的位数，堆的大小
	// 竞技场，以及L1和L2竞技场地图大小由
	// 
	// （1<<addr bits）=竞技场大小*L1条目*L2条目
	// 
	// 目前，我们通过以下方式来平衡它们：
	// 
	// /平台地址位竞技场大小L1条目L2条目
	// -----------------------------------
	// */64位48 64MB 1 4M（32MB）
	// ios/arm64 33 4MB 1 2048（8KB）
	// */32位32 4MB 1 1024（4KB）
	// */mips（le）31 4MB 1 512（2KB）

	// heapArenaBytes是堆竞技场的大小。堆
	// 由大小为heapArenaBytes的映射组成，这些映射与
	// heapArenaBytes对齐。最初的堆映射是一个竞技场。
	// 
	// 在64位非Windows上当前为64MB，在
	// 32位和Windows上当前为4MB。我们在Windows上使用更小的竞技场，因为所有提交的内存都会被加载到进程中，即使它没有被触碰。因此，对于
	// 堆较小的进程，映射的竞技场空间需要相称。
	// 这对于种族检测器
	// 尤其重要，因为它显著增加了承诺
	// 内存的成本。
	heapArenaBytes = 1 << logHeapArenaBytes

	// logHeapArenaBytes是heapArenaBytes的logu 2。为了清楚起见，
	// 在可能的情况下更喜欢使用heapArenaBytes（我们需要
	// 常量来计算其他一些常量）。
	logHeapArenaBytes = (6+20)*(_64bit*(1-goos.IsWindows)*(1-goarch.IsWasm)*(1-goos.IsIos*goarch.IsArm64)) + (2+20)*(_64bit*goos.IsWindows) + (2+20)*(1-_64bit) + (2+20)*goarch.IsWasm + (2+20)*goos.IsIos*goarch.IsArm64

	// heapArenaBitmapBytes是每个堆竞技场位图的大小。
	heapArenaBitmapBytes = heapArenaBytes / (goarch.PtrSize * 8 / 2)

	pagesPerArena = heapArenaBytes / pageSize

	// arenaL1Bits是一级竞技场地图覆盖的竞技场编号
	// 的位数。
	// 
	// 这个数字应该很小，因为一级竞技场
	// 地图需要在
	// 二进制的BSS中使用PtrSize*（1<<arenaL1Bits）空间。它可以是零，在这种情况下，一级
	// 索引实际上未被使用。
	// 这有一个性能优势，因为生成的代码可能更高效，但代价是要有一个大的L2映射。
	// 
	// 我们在64位Windows上使用L1映射，因为竞技场大小
	// 很小，但地址空间仍然是48位，而
	// 拥有一个大型L2的成本很高。
	arenaL1Bits = 6 * (_64bit * goos.IsWindows)

	// arenaL2Bits是二级竞技场索引覆盖的竞技场编号
	// 的位数。
	// 
	// 每个竞技场地图分配的大小与
	// 1<<arenal2比特成正比，因此重要的是不要太大。48位导致32MB竞技场索引分配，这大约是实际阈值。
	arenaL2Bits = heapAddrBits - logHeapArenaBytes - arenaL1Bits

	// arenaL1Shift是将竞技场帧移位的位数
	// number，用于将索引计算到一级竞技场地图中。
	arenaL1Shift = arenaL2Bits

	// arenaBits是综合竞技场地图索引中的总位。
	// 这是分成L1竞技场地图和
	// L2竞技场地图的索引。
	arenaBits = arenaL1Bits + arenaL2Bits

	// arenaBaseOffset是与堆竞技场映射中的索引0相对应的指针值。在amd64上，地址空间为48位，符号扩展为64位。这个偏移量允许我们处理“负”地址（或者
	// 如果被视为无符号，则为高位地址）。
	// 
	// 在aix/ppc64上，此偏移量允许将HeapArbits保留为
	// 48。否则，为了处理mmap地址
	// （范围0x0a00000000000000-0x0AFFFFFFFFFFFFFF），它将是60。但是在这个
	// 案例中，在（s*pageAlloc）中保留的内存。init for chunk 
	// 正在导致严重的速度减慢。
	// 
	// 在其他平台上，用户地址空间是连续的
	// 从0开始，因此不需要偏移。
	arenaBaseOffset = 0xffff800000000000*goarch.IsAmd64 + 0x0a00000000000000*goos.IsAix
	// 这个常数的一个类型化版本，它将使它变成侏儒（用于viewcore）。
	arenaBaseOffsetUintptr = uintptr(arenaBaseOffset)

	// 运行垃圾收集的最大线程数。
	// 2、3和4都是合理的最大值，具体取决于机器的硬件细节。垃圾
	// 收集器可以很好地扩展到32个CPU。
	_MaxGcproc = 32

	// minLegalPointer是最小的合法指针。
	// 这是可能的最小架构页面大小，
	// 因为我们假设第一个页面从未被映射。
	// 
	// 这应该与编译器中的minZeroPage一致。
	minLegalPointer uintptr = 4096
)

// physPageSize是操作系统物理页面的字节大小。
// 映射和取消映射操作必须以
// physPageSize的倍数进行。
// 
// 这必须在
// mallocinit之前由操作系统初始化代码（通常在osinit中）设置。
var physPageSize uintptr

// physHugePageSize是操作系统默认的物理巨型
// 页面大小的字节大小，其分配对应用程序是不透明的。假设
// 并验证为二的幂。
// 
// 如果设置，则必须在
// mallocinit之前由操作系统初始化代码（通常在osinit中）设置。但是，设置它是可选的，保留默认的
// 值总是安全的（尽管可能效率较低）。
// 
// 由于physHugePageSize总是假定为二的幂，因此physHugePageShift定义为physHugePageSize==1<<physHugePageShift。
// physHugePageShift的目的是避免在
// 性能关键函数中进行分割。
var (
	physHugePageSize  uintptr
	physHugePageShift uint
)

// OS内存管理抽象层
// 
// 运行时管理的地址空间区域在任何给定时间都可能处于四种
// 状态之一：
// 1）无-未保留和未映射，任何区域的默认状态。
// 2）保留-由运行时拥有，但访问它会导致错误。
// 不计入进程的内存占用。
// 3）Prepared-保留，不由物理内存支持（尽管操作系统可能会延迟实现）。可以有效地过渡到
// 就绪。在这样的区域中访问内存是未定义的（可能是
// 错误，可能会返回意外的零等）。
// 4）就绪-可以安全访问。
// 
// 这组状态对于支持所有
// 当前支持的平台来说，是绝对必要的。一个人可以一个也没有，保留，并且
// 准备好。然而，准备好的状态为我们提供了执行
// /目的的灵活性。例如，在POSIX-y操作系统上，Reserved通常是一个设置了PROT_NONE的
// 私有匿名mmap'd区域，要将
// 转换为Ready，需要设置PROT_READ | PROT_WRITE。然而，
// Prepared的规格不足让我们可以使用MADV_FREE从
// Ready to Prepared转换。因此，有了准备好的状态，我们可以在早期设置一次权限
// 位，我们可以有效地告诉操作系统，当我们不严格需要页面时，可以自由地从我们这里拿走页面。
// 
// 对于每个操作系统，都定义了一组通用的帮助程序，用于转换
// 这些状态之间的内存区域。帮助程序如下：
// 
// sysAlloc将操作系统选择的内存区域从无转换为就绪。
// 更具体地说，它从
// 操作系统获得大量的零内存，通常大约为100千字节或1兆字节。此内存始终可立即使用。
// 
// sysFree将内存区域从任何状态转换为无状态。因此，它
// 无条件返回内存。如果在分配过程中检测到内存不足错误
// 或在地址空间中划出
// 的对齐部分，则使用该函数。只有当sysReserve始终
// 返回一个与堆分配器的对齐
// 限制对齐的内存区域时，如果sysFree是no op，这是可以的。
// 
// sysReserve将内存区域从无转换为保留。它保留
// 地址空间，以便在访问
// （通过权限或不提交内存）时导致致命错误。这样的保留是
// 因此从来没有物理内存支持。
// 如果传递给它的指针为非nil，则调用方希望那里有
// 保留，但如果该位置不可用，sysReserve仍然可以选择另一个
// 位置。
// 注意：sysReserve返回操作系统对齐的内存，但堆分配器
// 可能使用更大的对齐，因此调用方必须小心重新对齐sysReserve获得的
// 内存。
// 
// sysMap将内存区域从保留转换为准备。它确保
// 内存区域可以有效地转换为就绪状态。
// 
// sysUsed将内存区域从准备状态转换为就绪状态。它通知
// 操作系统需要该内存区域，并确保可以安全访问该区域
// 。对于没有明确提交步骤且难以超过提交限制的系统，这通常是不可操作的，但对于例如
// Windows来说，这是至关重要的。
// 
// sysUnused将内存区域从就绪转换为准备就绪。它通知
// 操作系统，支持此内存区域的物理页不再需要，可以重新用于其他目的。
// sysUnused内存区域的内容被视为无效，在调用sysUsed之前，该区域不得再次被
// 访问。
// 
// sysFault将内存区域从就绪或准备状态转换为保留状态。它
// 标记了一个区域，如果访问该区域，它将始终出现故障。仅用于
// 调试运行时。

func mallocinit() {
	if class_to_size[_TinySizeClass] != _TinySize {
		throw("bad TinySizeClass")
	}

	if heapArenaBitmapBytes&(heapArenaBitmapBytes-1) != 0 {
		// heapBits希望位图上的模运算
		// 地址可以工作。
		throw("heapArenaBitmapBytes not a power of 2")
	}

	// 为统计表复制类大小。
	for i := range class_to_size {
		memstats.by_size[i].size = uint32(class_to_size[i])
	}

	// 检查physPageSize。
	if physPageSize == 0 {
		// 操作系统初始化代码无法获取物理页面大小。
		throw("failed to get system page size")
	}
	if physPageSize > maxPhysPageSize {
		print("system page size (", physPageSize, ") is larger than maximum page size (", maxPhysPageSize, ")\n")
		throw("bad system page size")
	}
	if physPageSize < minPhysPageSize {
		print("system page size (", physPageSize, ") is smaller than minimum page size (", minPhysPageSize, ")\n")
		throw("bad system page size")
	}
	if physPageSize&(physPageSize-1) != 0 {
		print("system page size (", physPageSize, ") must be a power of 2\n")
		throw("bad system page size")
	}
	if physHugePageSize&(physHugePageSize-1) != 0 {
		print("system huge page size (", physHugePageSize, ") must be a power of 2\n")
		throw("bad system huge page size")
	}
	if physHugePageSize > maxPhysHugePageSize {
		// physHugePageSize大于支持的最大页面大小。
		// 不要像在其他情况下一样在这里抛出，因为系统以这种方式配置
		// 并没有错，我们只是没有支持它们的代码。
		// 相反，将巨大的页面大小设置为零。
		physHugePageSize = 0
	}
	if physHugePageSize != 0 {
		// 因为physHugePageSize是2的幂，所以增加physHugePageShift就足够了，直到1<<physHugePageShift==physHugePageSize。
		for 1<<physHugePageShift != physHugePageSize {
			physHugePageShift++
		}
	}
	if pagesPerArena%pagesPerSpanRoot != 0 {
		print("pagesPerArena (", pagesPerArena, ") is not divisible by pagesPerSpanRoot (", pagesPerSpanRoot, ")\n")
		throw("bad pagesPerSpanRoot")
	}
	if pagesPerArena%pagesPerReclaimerChunk != 0 {
		print("pagesPerArena (", pagesPerArena, ") is not divisible by pagesPerReclaimerChunk (", pagesPerReclaimerChunk, ")\n")
		throw("bad pagesPerReclaimerChunk")
	}

	// 初始化堆。
	mheap_.init()
	mcache0 = allocmcache()
	lockInit(&gcBitsArenas.lock, lockRankGcBitsArenas)
	lockInit(&proflock, lockRankProf)
	lockInit(&globalAlloc.mutex, lockRankGlobalAlloc)

	// 创建初始竞技场增长提示。
	if goarch.PtrSize == 8 {
		// 在64位机器上，我们选择以下提示
		// 因为：
		// 
		// 1。从地址空间的中间开始
		// 可以更容易地扩展出一个连续范围
		// 而无需运行到其他映射。
		// 
		// 2。这使得Go堆地址在调试时更容易识别。
		// 
		// 3。gccgo中的堆栈扫描仍然是保守的，
		// 因此地址与其他数据的区别非常重要。
		// 
		// 从0x00c0开始表示有效内存地址
		// 将从0x00c0、0x00c1、。。。
		// 在little endian中，这是C000，c1 00。。。所有这些都不是有效的
		// UTF-8序列，而且它们与
		// ff（可能是一个公共字节）尽可能多。如果失败，我们尝试其他0xXXc0 
		// 地址。先前尝试使用0x11f8时，在线程分配期间导致OS X上出现内存不足错误
		// 。0x00c0与
		// AddressSanitarizer产生冲突，后者保留0x0100之前的所有内存。
		// 这些选择降低了保守的垃圾收集器
		// 不收集内存的可能性，因为某些非指针内存块
		// 具有与内存地址匹配的位模式。
		// 
		// 然而，在arm64上，我们忽略了上述所有建议，并将
		// 分配设置为0x40<<32，因为当使用带有三级
		// 转换缓冲区的4k页面时，用户地址空间被限制为39位
		// 在ios/arm64上，地址空间更小。
		// 
		// 在AIX上，64位的MMAP从0x0A00000000000000开始。
		// 进程。
		for i := 0x7f; i >= 0; i-- {
			var p uintptr
			switch {
			case raceenabled:
				// TSAN运行时要求堆
				// 在范围内[0x00c000000000，
				// 0x00e000000000）。
				p = uintptr(i)<<32 | uintptrMask&(0x00c0<<32)
				if p >= uintptrMask&0x00e000000000 {
					continue
				}
			case GOARCH == "arm64" && GOOS == "ios":
				p = uintptr(i)<<40 | uintptrMask&(0x0013<<28)
			case GOARCH == "arm64":
				p = uintptr(i)<<40 | uintptrMask&(0x0040<<32)
			case GOOS == "aix":
				if i == 0 {
					// 我们不直接在0x0A000000000之后使用地址
					// 以避免与非go程序执行的其他MMAP发生冲突。
					continue
				}
				p = uintptr(i)<<40 | uintptrMask&(0xa0<<52)
			default:
				p = uintptr(i)<<40 | uintptrMask&(0x00c0<<32)
			}
			hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
			hint.addr = p
			hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
		}
	} else {
		// 在32位机器上，我们更关心
		// 如何保持可用堆的连续性。
		// 因此：
		// 
		// 1。我们预先为所有堆预留空间，这样它们就不会与堆交叉。它们是
		// /~258MB，所以这还不算太糟。（如果这是
		// 问题，我们可以在前端预留较小的空间。）
		// 
		// 2。我们提示堆从二进制文件
		// 结尾的正上方开始，这样我们就有最好的机会保持它
		// 连续。
		// 
		// 3。我们试图确定一个相当大的初始
		// 堆保留。

		const arenaMetaSize = (1 << arenaBits) * unsafe.Sizeof(heapArena{})
		meta := uintptr(sysReserve(nil, arenaMetaSize))
		if meta != 0 {
			mheap_.heapArenaAlloc.init(meta, arenaMetaSize, true)
		}

		// 我们想把竞技场放低一点，但是如果我们把
		// 与C代码联系起来，那么可能是全局构造函数
		// 调用了malloc并调整了进程的brk。
		// 查询brk，这样我们就可以避免试图在其上映射
		// 区域（这将导致内核将
		// 该区域放在其他地方，可能位于高
		// 地址）。
		procBrk := sbrk0()

		// 如果我们要求结束数据段，但
		// 操作系统需要更多的空间
		// 在我们开始分配之前，它会给出一个稍微高一点的指针。除了QEMU，它是
		// buggy，和往常一样：它不会向上调整指针
		// 。所以我们自己把它调高一点：
		// 1/4 MB，以远离正在运行的二进制图像。
		p := firstmoduledata.end
		if p < procBrk {
			p = procBrk
		}
		if mheap_.heapArenaAlloc.next <= p && p < mheap_.heapArenaAlloc.end {
			p = mheap_.heapArenaAlloc.end
		}
		p = alignUp(p+(256<<10), heapArenaBytes)
		// 因为我们担心
		// 32位上的碎片，我们尝试进行大量的初始保留。
		arenaSizes := []uintptr{
			512 << 20,
			256 << 20,
			128 << 20,
		}
		for _, arenaSize := range arenaSizes {
			a, size := sysReserveAligned(unsafe.Pointer(p), arenaSize, heapArenaBytes)
			if a != nil {
				mheap_.arena.init(uintptr(a), size, false)
				p = mheap_.arena.end // 下面的提示
				break
			}
		}
		hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
		hint.addr = p
		hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
	}
}

// sysAlloc为至少n个字节分配堆竞技场空间。
// 返回的指针始终是由
// h.arenas元数据对齐和支持的heapArenaBytes。返回的大小始终是
// heapArenaBytes的倍数。sysAlloc在失败时返回nil。
// 没有相应的自由函数。
// 
// sysAlloc返回处于保留状态的内存区域。该区域必须
// 转换为“准备好”，然后在使用前准备好。
// 
// h必须被锁定。
func (h *mheap) sysAlloc(n uintptr) (v unsafe.Pointer, size uintptr) {
	assertLockHeld(&h.lock)

	n = alignUp(n, heapArenaBytes)

	// 首先，尝试竞技场预订。
	v = h.arena.alloc(n, heapArenaBytes, &memstats.heap_sys)
	if v != nil {
		size = n
		goto mapped
	}

	// 尝试在提示地址增加堆。
	for h.arenaHints != nil {
		hint := h.arenaHints
		p := hint.addr
		if hint.down {
			p -= n
		}
		if p+n < p {
			// 我们不能用这个，所以不要问。
			v = nil
		} else if arenaIndex(p+n-1) >= 1<<arenaBits {
			// 外部可寻址堆。不能使用。
			v = nil
		} else {
			v = sysReserve(unsafe.Pointer(p), n)
		}
		if p == uintptr(v) {
			// 成功。更新提示。
			if !hint.down {
				p += n
			}
			hint.addr = p
			size = n
			break
		}
		// 失败。放弃此提示，然后尝试下一个提示。
		// 
		// TODO:如果sysReserve可以被
		// 告知只返回请求的地址，这将更干净。特别是，这已经是Windows的行为方式了，所以它将简化那里的事情。
		if v != nil {
			sysFree(v, n, nil)
		}
		h.arenaHints = hint.next
		h.arenaHintAlloc.free(unsafe.Pointer(hint))
	}

	if size == 0 {
		if raceenabled {
			// 竞态检测器假设堆位于
			// /[0x00c000000000，0x00e000000000），但我们
			// 这个区域的提示已经用完了。给
			// 一个很好的失败。
			throw("too many address space collisions for -race mode")
		}

		// 所有提示都失败了，所以我们将取内核给
		// /（充分对齐）的任何地址
		// /我们。
		v, size = sysReserveAligned(nil, n, heapArenaBytes)
		if v == nil {
			return nil, 0
		}

		// 创建扩展此区域的新提示。
		hint := (*arenaHint)(h.arenaHintAlloc.alloc())
		hint.addr, hint.down = uintptr(v), true
		hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
		hint = (*arenaHint)(h.arenaHintAlloc.alloc())
		hint.addr = uintptr(v) + size
		hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
	}

	// 检查是否存在错误指针或无法使用的指针。
	{
		var bad string
		p := uintptr(v)
		if p+size < p {
			bad = "region exceeds uintptr range"
		} else if arenaIndex(p) >= 1<<arenaBits {
			bad = "base outside usable address space"
		} else if arenaIndex(p+size-1) >= 1<<arenaBits {
			bad = "end outside usable address space"
		}
		if bad != "" {
			// 这在大多数体系结构上都是不可能的，但是调试起来会很混乱。
			print("runtime: memory allocated by OS [", hex(p), ", ", hex(p+size), ") not in usable address space: ", bad, "\n")
			throw("memory reservation exceeds address space limit")
		}
	}

	if uintptr(v)&(heapArenaBytes-1) != 0 {
		throw("misrounded allocation in sysAlloc")
	}

mapped:
	// 创建竞技场元数据。
	for ri := arenaIndex(uintptr(v)); ri <= arenaIndex(uintptr(v)+size-1); ri++ {
		l2 := h.arenas[ri.l1()]
		if l2 == nil {
			// 分配一张二级竞技场地图。
			l2 = (*[1 << arenaL2Bits]*heapArena)(persistentalloc(unsafe.Sizeof(*l2), goarch.PtrSize, nil))
			if l2 == nil {
				throw("out of memory allocating heap arena map")
			}
			atomic.StorepNoWB(unsafe.Pointer(&h.arenas[ri.l1()]), unsafe.Pointer(l2))
		}

		if l2[ri.l2()] != nil {
			throw("arena already initialized")
		}
		var r *heapArena
		r = (*heapArena)(h.heapArenaAlloc.alloc(unsafe.Sizeof(*r), goarch.PtrSize, &memstats.gcMiscSys))
		if r == nil {
			r = (*heapArena)(persistentalloc(unsafe.Sizeof(*r), goarch.PtrSize, &memstats.gcMiscSys))
			if r == nil {
				throw("out of memory allocating heap arena metadata")
			}
		}

		// 将竞技场添加到竞技场列表中。
		if len(h.allArenas) == cap(h.allArenas) {
			size := 2 * uintptr(cap(h.allArenas)) * goarch.PtrSize
			if size == 0 {
				size = physPageSize
			}
			newArray := (*notInHeap)(persistentalloc(size, goarch.PtrSize, &memstats.gcMiscSys))
			if newArray == nil {
				throw("out of memory allocating allArenas")
			}
			oldSlice := h.allArenas
			*(*notInHeapSlice)(unsafe.Pointer(&h.allArenas)) = notInHeapSlice{newArray, len(h.allArenas), int(size / goarch.PtrSize)}
			copy(h.allArenas, oldSlice)
			// 不要释放旧的备份数组，因为可能存在并发读卡器。由于我们
			// 每次将阵列加倍，这可能导致
			// 最多浪费2倍。
		}
		h.allArenas = h.allArenas[:len(h.allArenas)+1]
		h.allArenas[len(h.allArenas)-1] = ri

		// 以原子方式存储，以防在释放堆锁
		// 之前，新堆竞技场中的对象可见（这不应该发生，但
		// 没有什么缺点）。
		atomic.StorepNoWB(unsafe.Pointer(&l2[ri.l2()]), unsafe.Pointer(r))
	}

	// 告诉竞争检测器新的堆内存。
	if raceenabled {
		racemapshadow(v, size)
	}

	return
}

// sysreservealligned类似于sysReserve，但返回的指针是
// aligned to align bytes。它可以保留n或n+align字节，
// 因此返回保留的大小。
func sysReserveAligned(v unsafe.Pointer, size, align uintptr) (unsafe.Pointer, uintptr) {
	// 由于在使用此
	// 函数时，对齐范围相当大，我们不太可能偶然获得它，因此我们要求
	// 获得更大的区域，并删除我们不需要的部分。
	retries := 0
retry:
	p := uintptr(sysReserve(v, size+align))
	switch {
	case p == 0:
		return nil, 0
	case p&(align-1) == 0:
		// 我们很幸运，得到了一个对齐的区域，所以我们可以使用整个东西。
		return unsafe.Pointer(p), size + align
	case GOOS == "windows":
		// 在Windows上，我们不能释放
		// 保留的部分，所以我们释放整个内容，并重新保留对齐的子区域。这可能是比赛，
		// 所以我们可能不得不再试一次。
		sysFree(unsafe.Pointer(p), size+align, nil)
		p = alignUp(p, align)
		p2 := sysReserve(unsafe.Pointer(p), size)
		if p != uintptr(p2) {
			// 一定是比赛了。再试一次。
			sysFree(p2, size, nil)
			if retries++; retries == 100 {
				throw("failed to allocate aligned heap memory; too many retries")
			}
			goto retry
		}
		// 成功。
		return p2, size
	default:
		// 修剪未对齐的零件。
		pAligned := alignUp(p, align)
		sysFree(unsafe.Pointer(p), pAligned-p, nil)
		end := pAligned + size
		endLen := (p + size + align) - end
		if endLen > 0 {
			sysFree(unsafe.Pointer(end), endLen, nil)
		}
		return unsafe.Pointer(pAligned), size
	}
}

// 所有0字节分配的基址
var zerobase uintptr

// 如果下一个可用对象很快可用，nextFreeFast将返回下一个可用对象。
// 否则返回0。
func nextFreeFast(s *mspan) gclinkptr {
	theBit := sys.Ctz64(s.allocCache) // allocCache中有自由对象吗？
	if theBit < 64 {
		result := s.freeindex + uintptr(theBit)
		if result < s.nelems {
			freeidx := result + 1
			if freeidx%64 == 0 && freeidx != s.nelems {
				return 0
			}
			s.allocCache >>= uint(theBit + 1)
			s.freeindex = freeidx
			s.allocCount++
			return gclinkptr(result*s.elemsize + s.base())
		}
	}
	return 0
}

// nextFree返回缓存范围中的下一个可用对象（如果有）。
// 否则，它会用一个可用对象的跨度重新填充缓存，
// 返回该对象以及一个标志，表明这是一个沉重的
// 权重分配。如果是重量级分配，调用者必须确定是否需要启动新的GC循环或GC是否处于活动状态
// 此goroutine是否需要协助GC。
// 
// 必须在不可抢占的上下文中运行，否则
// c的所有者可能会更改。
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
	s = c.alloc[spc]
	shouldhelpgc = false
	freeIndex := s.nextFreeIndex()
	if freeIndex == s.nelems {
		// 跨度已满。
		if uintptr(s.allocCount) != s.nelems {
			println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
			throw("s.allocCount != s.nelems && freeIndex == s.nelems")
		}
		c.refill(spc)
		shouldhelpgc = true
		s = c.alloc[spc]

		freeIndex = s.nextFreeIndex()
	}

	if freeIndex >= s.nelems {
		throw("freeIndex is not valid")
	}

	v = gclinkptr(freeIndex*s.elemsize + s.base())
	s.allocCount++
	if uintptr(s.allocCount) > s.nelems {
		println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
		throw("s.allocCount > s.nelems")
	}
	return
}

// 分配一个字节大小的对象。
// 从per-P缓存的空闲列表中分配小对象。
// 大对象（>32KB）直接从堆中分配。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
	if gcphase == _GCmarktermination {
		throw("mallocgc called with gcphase == _GCmarktermination")
	}

	if size == 0 {
		return unsafe.Pointer(&zerobase)
	}
	userSize := size
	if asanenabled {
		// 作为一个运行库，malloc（）函数在用户请求的内存区域周围分配额外的内存，
		// 红色区域。红色区域标记为
		// 无法寻址。我们在Go中执行相同的操作，以检测溢出或
		// 下溢。
		size += computeRZlog(size)
	}

	if debug.malloc {
		if debug.sbrk != 0 {
			align := uintptr(16)
			if typ != nil {
				// TODO（奥斯汀）：这应该只是
				// align=uintptpr（typ.align）
				// 但在32位平台上只有4，即使typ中有uint64字段（请参见#599）。
				// 这会导致64位原子访问死机。
				// 因此，我们使用更严格的对齐方式，更好地匹配正常的分配器。
				if size&7 == 0 {
					align = 8
				} else if size&3 == 0 {
					align = 4
				} else if size&1 == 0 {
					align = 2
				} else {
					align = 1
				}
			}
			return persistentalloc(size, align, &memstats.other_sys)
		}

		if inittrace.active && inittrace.id == getg().goid {
			// 初始化函数在一个goroutine中顺序执行。
			inittrace.allocs += 1
		}
	}

	// assistG是为此分配收取的费用，如果
	// GC当前不活动，则为零。
	var assistG *g
	if gcBlackenEnabled != 0 {
		// 为此分配向当前用户G收费。
		assistG = getg()
		if assistG.m.curg != nil {
			assistG = assistG.m.curg
		}
		// 从G中收取分配费用。我们将在mallocgc结束时对
		// 进行内部碎片计算。
		assistG.gcAssistBytes -= int64(size)

		if assistG.gcAssistBytes < 0 {
			// 这个G负债了。分配前，协助总承包商纠正
			// 此错误。这必须在禁用抢占之前发生。
			gcAssistAlloc(assistG)
		}
	}

	// 设置mp。mallocing防止被GC抢先。
	mp := acquirem()
	if mp.mallocing != 0 {
		throw("malloc deadlock")
	}
	if mp.gsignal == getg() {
		throw("malloc during signal")
	}
	mp.mallocing = 1

	shouldhelpgc := false
	dataSize := userSize
	c := getMCache(mp)
	if c == nil {
		throw("mallocgc called without a P or outside bootstrapping")
	}
	var span *mspan
	var x unsafe.Pointer
	noscan := typ == nil || typ.ptrdata == 0
	// 在某些情况下，块归零可以有利地（为了减少延迟）
	// 延迟到可以抢占时；延迟归零跟踪该状态。
	delayedZeroing := false
	if size <= maxSmallSize {
		if noscan && size < maxTinySize {
			// 微型分配器。du jian defg
			// 在所有子对象都无法访问时被释放。子对象
			// 必须是noscan（没有指针），这确保了
			// 潜在的内存浪费量是有限的。
			// 
			// 用于组合（maxTinySize）的内存块大小可调。
			// 当前设置为16字节，这与2x最坏情况下的内存有关
			// 损耗（当除一个子对象之外的所有子对象都无法访问时）。
			// 8字节完全不会造成浪费，但提供的合并机会较少。
			// 32字节提供了更多的组合机会，但可能导致4倍的最坏情况下的浪费。
			// 无论区块大小，最佳获胜案例为8x。
			// 
			// 不能显式释放从微小分配器获取的对象。
			// 所以当一个对象被显式释放时，我们确保
			// 它的大小>=maxTinySize。
			// 
			// SetFinalizer对于可能来自微小分配器的对象有一个特殊情况，在这种情况下，它允许为内存块的内部字节设置终结器
			// 。
			// 
			// tiny分配器的主要目标是小字符串和
			// 独立转义变量。在json基准测试
			// 上，分配器将分配数量减少约12%，
			// 将堆大小减少约20%。
			off := c.tinyoffset
			// 对齐所需（保守）对齐的小指针。
			if size&7 == 0 {
				off = alignUp(off, 8)
			} else if goarch.PtrSize == 4 && size == 12 {
				// 在32位
				// 系统上，将12字节的对象保守地对齐到8字节，这样一来，第一个字段是64位
				// 值的对象将对齐到8字节，并且不会导致
				// 原子访问出现故障。见第37262期。
				// TODO（mknyszek）：如果问题36606 
				// 得到解决，请删除此解决方法。
				off = alignUp(off, 8)
			} else if size&3 == 0 {
				off = alignUp(off, 4)
			} else if size&1 == 0 {
				off = alignUp(off, 2)
			}
			if off+size <= maxTinySize && c.tiny != 0 {
				// 该对象适合现有的小块。
				x = unsafe.Pointer(c.tiny + off)
				c.tinyoffset = off + size
				c.tinyAllocs++
				mp.mallocing = 0
				releasem(mp)
				return x
			}
			// 分配一个新的maxTinySize块。
			span = c.alloc[tinySpanClass]
			v := nextFreeFast(span)
			if v == 0 {
				v, span, shouldhelpgc = c.nextFree(tinySpanClass)
			}
			x = unsafe.Pointer(v)
			(*[2]uint64)(x)[0] = 0
			(*[2]uint64)(x)[1] = 0
			// 看看我们是否需要根据剩余的可用空间量，用新的块替换现有的小块。
			if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
				// 注意：种族检测器打开时禁用，请参阅此函数末尾附近的注释。
				c.tiny = uintptr(x)
				c.tinyoffset = size
			}
			size = maxTinySize
		} else {
			var sizeclass uint8
			if size <= smallSizeMax-8 {
				sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
			} else {
				sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
			}
			size = uintptr(class_to_size[sizeclass])
			spc := makeSpanClass(sizeclass, noscan)
			span = c.alloc[spc]
			v := nextFreeFast(span)
			if v == 0 {
				v, span, shouldhelpgc = c.nextFree(spc)
			}
			x = unsafe.Pointer(v)
			if needzero && span.needzero != 0 {
				memclrNoHeapPointers(unsafe.Pointer(v), size)
			}
		}
	} else {
		shouldhelpgc = true
		// 对于大型分配，请跟踪归零状态，以便
		// 稍后可以在可抢占上下文中进行批量归零。
		span = c.allocLarge(size, noscan)
		span.freeindex = 1
		span.allocCount = 1
		size = span.elemsize
		x = unsafe.Pointer(span.base())
		if needzero && span.needzero != 0 {
			if noscan {
				delayedZeroing = true
			} else {
				memclrNoHeapPointers(x, size)
				// 我们在理论上已经清除了几乎整个跨度，
				// 并且可以采取额外的步骤，实际清除整个过程。但是，不要这样做。
				// 未清除部分的任何GC位都将为零，而且一旦释放，它只会变成
				// needzero=1。
			}
		}
	}

	var scanSize uintptr
	if !noscan {
		heapBitsSetType(uintptr(x), size, dataSize, typ)
		if dataSize > typ.size {
			// 数组分配。如果有任何
			// 指针，GC必须扫描到最后一个
			// 元素。
			if typ.ptrdata != 0 {
				scanSize = dataSize - typ.size + typ.ptrdata
			}
		} else {
			scanSize = typ.ptrdata
		}
		c.scanAlloc += scanSize
	}

	// 确保上面将x初始化为
	// 的存储在
	// 之前键入安全内存并设置堆位。调用方可以使x对垃圾
	// 收集器可见。否则，在弱序机器上，垃圾收集器可以跟随指向x的指针，但可以看到未初始化的内存或陈旧的堆位。
	publicationBarrier()

	// 在GC期间分配黑色。
	// 所有插槽均为零，因此无需扫描。
	// 这可能是与GC的竞赛，所以如果可能有
	// 标记位的竞赛，请以原子方式进行。
	if gcphase != _GCoff {
		gcmarknewobject(span, uintptr(x), size, scanSize)
	}

	if raceenabled {
		racemalloc(x, size)
	}

	if msanenabled {
		msanmalloc(x, size)
	}

	if asanenabled {
		// 我们应该只按照用户要求的大小读/写内存。huang jian defg
		// 分配的内存大于所需的userSize，它还将包括
		// redzone和一些其他填充字节。
		rzBeg := unsafe.Add(x, userSize)
		asanpoison(rzBeg, size-userSize)
		asanunpoison(x, userSize)
	}

	if rate := MemProfileRate; rate > 0 {
		// 注意缓存c仅在获取m时有效；请参阅#47302 
		if rate != 1 && size < c.nextSample {
			c.nextSample -= size
		} else {
			profilealloc(mp, x, size)
		}
	}
	mp.mallocing = 0
	releasem(mp)

	// 在可能发生抢占的情况下，无指针数据可能会延迟归零。
	// x将保持内存的活力。
	if delayedZeroing {
		if !noscan {
			throw("delayed zeroing on data that may contain pointers")
		}
		memclrNoHeapPointersChunked(size, x) // 这是一个可能的抢占点：参见#47302 
	}

	if debug.malloc {
		if debug.allocfreetrace != 0 {
			tracealloc(x, size, typ)
		}

		if inittrace.active && inittrace.id == getg().goid {
			// 初始化函数在一个goroutine中顺序执行。
			inittrace.bytes += uint64(size)
		}
	}

	if assistG != nil {
		// 说明辅助系统中的内部碎片
		// 现在我们知道了债务。
		assistG.gcAssistBytes -= int64(size - dataSize)
	}

	if shouldhelpgc {
		if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
			gcStart(t)
		}
	}

	if raceenabled && noscan && dataSize < maxTinySize {
		// Pad tinysize分配，使其与tinyalloc地区的末端
		// 一致。这确保了任何超出对象顶端的算术
		// 都是可检测的
		// checkptr（第38872期）。
		// 请注意，我们在启用此功能时禁用tinyalloc。
		// TODO:此填充仅在启用竞赛检测器
		// 时执行。如果任何包
		// 是用checkptr编译的，那么启用它会很好，但要检测到它（尤其是在编译时），没有简单的方法。
		// TODO:为所有分配启用此填充，而不仅仅是
		// tinyalloc分配。这很棘手，因为指针映射。
		// 也许只是所有的noscan对象？
		x = add(x, size-dataSize)
	}

	return x
}

// memclrNoHeapPointers chunked在要归零的缓冲区块上反复调用memclrNoHeapPointers 
// ，同时有抢占的机会
// 。memclrNoHeapPointers不包含安全点，而且
// 不能被抢占，因此这提供了一个仍然有效的
// 块拷贝，也可以在合理的粒度上被抢占。
// 
// 小心使用；如果被清除的数据被标记为包含
// 指针，这允许GC在全部清除之前运行。
func memclrNoHeapPointersChunked(size uintptr, x unsafe.Pointer) {
	v := uintptr(x)
	// 这是通过基准测试得到的。128k太小，512k太大。
	const chunkBytes = 256 * 1024
	vsize := v + size
	for voff := v; voff < vsize; voff = voff + chunkBytes {
		if getg().preempt {
			// 可能持有锁，例如，分析
			goschedguarded()
		}
		// 清除最小（可用，整块）字节
		n := vsize - voff
		if n > chunkBytes {
			n = chunkBytes
		}
		memclrNoHeapPointers(unsafe.Pointer(voff), n)
	}
}

// 实现新内置
// 编译器（前端和SSA后端）知道此函数的签名
// 
func newobject(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// go:linkname reflect_unsafe_new reflect。unsafe_New 
func reflect_unsafe_New(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// go:linkname reflectlite_unsafe_New internal/reflectlite。unsafe_New 
func reflectlite_unsafe_New(typ *_type) unsafe.Pointer {
	return mallocgc(typ.size, typ, true)
}

// newarray分配一个类型为typ的n个元素的数组。
func newarray(typ *_type, n int) unsafe.Pointer {
	if n == 1 {
		return mallocgc(typ.size, typ, true)
	}
	mem, overflow := math.MulUintptr(typ.size, uintptr(n))
	if overflow || mem > maxAlloc || n < 0 {
		panic(plainError("runtime: allocation size out of range"))
	}
	return mallocgc(mem, typ, true)
}

// go:linkname reflect_unsafe_NewArray reflect。unsafe_NewArray 
func reflect_unsafe_NewArray(typ *_type, n int) unsafe.Pointer {
	return newarray(typ, n)
}

func profilealloc(mp *m, x unsafe.Pointer, size uintptr) {
	c := getMCache(mp)
	if c == nil {
		throw("profilealloc called without a P or outside bootstrapping")
	}
	c.nextSample = nextSample()
	mProf_Malloc(x, size)
}

// nextSample返回堆分析的下一个采样点。目标是平均每个MemProfileRate字节对分配进行抽样，但在分配时间线上完全随机分布；这个
// 对应于参数为MemProfileRate的泊松过程。在Poisson-
// 过程中，两个样本之间的距离遵循指数分布（exp（MemProfileRate）），因此最佳返回值是从平均值为MemProfileRate的指数分布中获取的随机
// 数。
func nextSample() uintptr {
	if MemProfileRate == 1 {
		// 调用者将我们的返回值分配给
		// mcache。下一个样本，但当速率为1时，下一个样本不使用
		// 。因此，避免下面的数学运算，只需返回一些内容。
		return 0
	}
	if GOOS == "plan9" {
		// 计划9不支持注释处理程序中的浮点。
		if g := getg(); g == g.m.gsignal {
			return nextSampleNoFP()
		}
	}

	return uintptr(fastexprand(MemProfileRate))
}

// fastexprand返回指数分布中的随机数，
// 指定的平均值。
func fastexprand(mean int) int32 {
	// 避免溢出。最大可能的步长是
	// /-ln（1/（1<<randomBitCount））*平均值，大约20*平均值。
	switch {
	case mean > 0x7000000:
		mean = 0x7000000
	case mean == 0:
		return 0
	}

	// 取指数分布exp（-mean*x）的随机样本。
	// 概率分布函数是mean*exp（-mean*x），所以CDF是
	// p=1-exp（-mean*x），所以
	// /q=1-p==exp（-mean*x）
	// /x=-log_e（q）*；使用log_2提高效率
	const randomBitCount = 26
	q := fastrandn(1<<randomBitCount) + 1
	qlog := fastlog2(float64(q)) - randomBitCount
	if qlog > 0 {
		qlog = 0
	}
	const minusLog2 = -0.6931471805599453 // /-ln（2）
	return int32(qlog*(minusLog2*float64(mean))) + 1
}

// nextSampleNoFP与nextSample类似，但使用更旧、更简单的代码来避免浮点。
func nextSampleNoFP() uintptr {
	// 设置第一次分配样本大小。
	rate := MemProfileRate
	if rate > 0x3fffffff { // make 2*rate not overflow 
		rate = 0x3fffffff
	}
	if rate != 0 {
		return uintptr(fastrandn(uint32(2 * rate)))
	}
	return 0
}

type persistentAlloc struct {
	base *notInHeap
	off  uintptr
}

var globalAlloc struct {
	mutex
	persistentAlloc
}

// persistentChunkSize是我们在增长
// persistentAlloc时分配的字节数。
const persistentChunkSize = 256 << 10

// persistentChunks是我们已分配的所有持久块的列表。该列表通过
// 持久块中的第一个单词进行维护。这是原子更新。
var persistentChunks *notInHeap

// 可以分配小块的sysAlloc的包装器。
// 没有相关的自由操作。
// 用于函数/类型/调试相关的持久数据。
// 如果align为0，则使用默认的align（当前为8）。
// 返回的内存将归零。ABCFDG 
func persistentalloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer {
	var p *notInHeap
	systemstack(func() {
		p = persistentalloc1(size, align, sysStat)
	})
	return unsafe.Pointer(p)
}

// 必须在系统堆栈上运行，因为堆栈增长可以（重新）调用它。
// 见第9174期。
// go:systemstack 
func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *notInHeap {
	const (
		maxBlock = 64 << 10 // windows上的VM保留粒度为64K 
	)

	if size == 0 {
		throw("persistentalloc: size == 0")
	}
	if align != 0 {
		if align&(align-1) != 0 {
			throw("persistentalloc: align is not a power of 2")
		}
		if align > _PageSize {
			throw("persistentalloc: align is too large")
		}
	} else {
		align = 8
	}

	if size >= maxBlock {
		return (*notInHeap)(sysAlloc(size, sysStat))
	}

	mp := acquirem()
	var persistent *persistentAlloc
	if mp != nil && mp.p != 0 {
		persistent = &mp.p.ptr().palloc
	} else {
		lock(&globalAlloc.mutex)
		persistent = &globalAlloc.persistentAlloc
	}
	persistent.off = alignUp(persistent.off, align)
	if persistent.off+size > persistentChunkSize || persistent.base == nil {
		persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys))
		if persistent.base == nil {
			if persistent == &globalAlloc.persistentAlloc {
				unlock(&globalAlloc.mutex)
			}
			throw("runtime: cannot allocate memory")
		}

		// 将新块添加到persistentChunks列表中。
		for {
			chunks := uintptr(unsafe.Pointer(persistentChunks))
			*(*uintptr)(unsafe.Pointer(persistent.base)) = chunks
			if atomic.Casuintptr((*uintptr)(unsafe.Pointer(&persistentChunks)), chunks, uintptr(unsafe.Pointer(persistent.base))) {
				break
			}
		}
		persistent.off = alignUp(goarch.PtrSize, align)
	}
	p := persistent.base.add(persistent.off)
	persistent.off += size
	releasem(mp)
	if persistent == &globalAlloc.persistentAlloc {
		unlock(&globalAlloc.mutex)
	}

	if sysStat != &memstats.other_sys {
		sysStat.add(int64(size))
		memstats.other_sys.add(-int64(size))
	}
	return p
}

// inPersistentAlloc报告p是否指向由
// persistentalloc分配的内存。这必须是nosplit，因为它由
// cgo检查代码调用，该代码由写屏障代码调用。
// go:nosplit 
func inPersistentAlloc(p uintptr) bool {
	chunk := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&persistentChunks)))
	for chunk != 0 {
		if p >= chunk && p < chunk+persistentChunkSize {
			return true
		}
		chunk = *(*uintptr)(unsafe.Pointer(chunk))
	}
	return false
}

// linearloc是一个简单的线性分配器，它预先保留一个区域
// 内存，然后根据需要将该区域映射到就绪状态
// 中。
// 
// 调用方负责锁定。
type linearAlloc struct {
	next   uintptr // 下一个可用字节
	mapped uintptr // 映射空间结束后的一个字节
	end    uintptr // 保留空间结束

	mapMemory bool // 如果为真
}

func (l *linearAlloc) init(base, size uintptr, mapMemory bool) {
	if base+size < base {
		// 切掉最后一个字节。运行时没有准备好处理边界可能溢出的情况。
		// 不过，请保留该内存，这样我们以后就不会将其映射到
		// 了。
		size -= 1
	}
	l.next, l.mapped = base, base
	l.end = base + size
	l.mapMemory = mapMemory
}

func (l *linearAlloc) alloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer {
	p := alignUp(l.next, align)
	if p+size > l.end {
		return nil
	}
	l.next = p + size
	if pEnd := alignUp(l.next-1, physPageSize); pEnd > l.mapped {
		if l.mapMemory {
			// 从保留到准备到就绪的转换。
			sysMap(unsafe.Pointer(l.mapped), pEnd-l.mapped, sysStat)
			sysUsed(unsafe.Pointer(l.mapped), pEnd-l.mapped)
		}
		l.mapped = pEnd
	}
	return unsafe.Pointer(p)
}

// notInHeap是由较低级别的分配器分配的堆外内存，如sysAlloc或persistentaloc。
// 
// 一般来说，最好使用标记为go:notinheap，
// 的实类型，但对于不可能的情况（如在分配器中），这是一个泛型。
// 
// TODO:是否将其用作sysAlloc、persistentAlloc等的返回类型？
// 
// go:notinheap 
type notInHeap struct{}

func (p *notInHeap) add(bytes uintptr) *notInHeap {
	return (*notInHeap)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + bytes))
}

// computeRZlog计算红色区域的大小。
// 参考编译器的实现-rt。
func computeRZlog(userSize uintptr) uintptr {
	switch {
	case userSize <= (64 - 16):
		return 16 << 0
	case userSize <= (128 - 32):
		return 16 << 1
	case userSize <= (512 - 64):
		return 16 << 2
	case userSize <= (4096 - 128):
		return 16 << 3
	case userSize <= (1<<14)-256:
		return 16 << 4
	case userSize <= (1<<15)-512:
		return 16 << 5
	case userSize <= (1<<16)-1024:
		return 16 << 6
	default:
		return 16 << 7
	}
}
